package com.linkoog.devtools.navigation;

import com.intellij.codeInsight.CodeInsightActionHandler;
import com.intellij.codeInsight.CodeInsightBundle;
import com.intellij.codeInsight.TargetElementUtil;
import com.intellij.codeInsight.actions.BaseCodeInsightAction;
import com.intellij.codeInsight.navigation.*;
import com.intellij.ide.util.EditSourceUtil;
import com.intellij.lang.documentation.psi.PsiElementDocumentationTarget;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.TextRange;
import com.intellij.platform.backend.documentation.DocumentationTarget;
import com.intellij.platform.backend.documentation.PsiDocumentationTargetProvider;
import com.intellij.pom.Navigatable;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.search.searches.DefinitionsScopedSearch;
import com.intellij.psi.util.PsiUtilCore;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.List;

import static com.intellij.codeInsight.navigation.CtrlMouseDataKt.getReferenceRanges;

//import static com.intellij.codeInsight.navigation.BaseCtrlMouseInfo.getReferenceRanges;

/**
 *  com.intellij.codeInsight.navigation.actions.GotoImplementationAction
 */
public class GotoImplementationAction extends BaseCodeInsightAction implements CtrlMouseAction {

    @NotNull
    @Override
    protected CodeInsightActionHandler getHandler() {
        return new GotoImplementationHandler();
    }

    @Override
    protected boolean isValidForLookup() {
        return true;
    }

    @Override
    public void update(@NotNull final AnActionEvent event) {
        if (!DefinitionsScopedSearch.INSTANCE.hasAnyExecutors()) {
            event.getPresentation().setVisible(false);
        }
        else {
            super.update(event);
        }
    }

    private static @NotNull PsiElement @Nullable [] targetElements(@NotNull Editor editor, int offset) {
        final PsiElement element = TargetElementUtil.getInstance().findTargetElement(editor, ImplementationSearcher.getFlags(), offset);
        return new ImplementationSearcher() {
            @Override
            protected PsiElement @NotNull [] searchDefinitions(final PsiElement element, Editor editor) {
                final List<PsiElement> found = new ArrayList<>(2);
                DefinitionsScopedSearch.search(element, getSearchScope(element, editor)).forEach(psiElement -> {
                    found.add(psiElement);
                    return found.size() != 2;
                });
                return PsiUtilCore.toPsiElementArray(found);
            }
        }.searchImplementations(editor, element, offset);
    }

    private static @Nullable PsiElement targetElement(@NotNull PsiElement targetElement) {
        Navigatable descriptor = EditSourceUtil.getDescriptor(targetElement);
        return descriptor != null && descriptor.canNavigate() && targetElement.isPhysical()
                ? targetElement
                : null;
    }

    /*public @Nullable CtrlMouseInfo getCtrlMouseInfo(@NotNull Editor editor, @NotNull PsiFile file, int offset) {
        PsiElement elementAtPointer = file.findElementAt(offset);
        if (elementAtPointer == null) {
            return null;
        }
        PsiElement[] targetElements = targetElements(editor, offset);
        if (targetElements == null || targetElements.length == 0) {
            return null;
        }
        else if (targetElements.length > 1) {
            return new MultipleTargetElementsInfo(elementAtPointer);
        }
        else {
            PsiElement targetElement = targetElement(targetElements[0]);
            if (targetElement == null) {
                return null;
            }
            return new SingleTargetElementInfo(elementAtPointer, targetElement);
        }
    }*/

    @Override
    public @Nullable CtrlMouseData getCtrlMouseData(@NotNull Editor editor, @NotNull PsiFile file, int offset) {
        final PsiElement elementAtPointer = file.findElementAt(offset);
        if (elementAtPointer == null) {
            return null;
        }
        PsiElement[] targetElements = targetElements(editor, offset);
        if (targetElements == null || targetElements.length == 0) {
            return null;
        }
        else if (targetElements.length > 1) {
            return new CtrlMouseData(getReferenceRanges(elementAtPointer), true, CodeInsightBundle.message("multiple.implementations.tooltip"), null);
        }
        else {
            PsiElement targetElement = targetElement(targetElements[0]);
            if (targetElement == null) {
                return null;
            }
            return psiCtrlMouseData(elementAtPointer, targetElement);
        }
    }

    private CtrlMouseData psiCtrlMouseData(PsiElement leafElement, PsiElement targetElement){
        return targetCtrlMouseData(getReferenceRanges(leafElement),
                isNavigatableQuickDoc(leafElement, targetElement),
                psiDocumentationTarget(targetElement, leafElement)
        );
    }

    private CtrlMouseData targetCtrlMouseData(List<TextRange> ranges, Boolean isNavigatable, DocumentationTarget target){
        return new CtrlMouseData(ranges, isNavigatable, target == null ? null : target.computeDocumentationHint(),target);
    }

    private boolean isNavigatableQuickDoc(PsiElement source, PsiElement target){
        if (source == null){
            return target != null;
        } else {
            return target != source && target != source.getParent();
        }
    }

    private DocumentationTarget psiDocumentationTarget(PsiElement element, PsiElement originalElement) {
        for (PsiDocumentationTargetProvider ext : PsiDocumentationTargetProvider.EP_NAME.getExtensionList()) {
            final DocumentationTarget target = ext.documentationTarget(element, originalElement);
            if (target != null){
                return target;
            }
        }
        return new PsiElementDocumentationTarget(element.getProject(), element, originalElement);
    }


}
